#Copyright (C) Nial Peters 2012
#
#This file is part of AvoScan.
#
#AvoScan is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#AvoScan is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with AvoScan.  If not, see <http://www.gnu.org/licenses/>.

"""
The manual module provides two wx.Panel subclasses that allow graphical 
control of the scanner manually through use of a slider and some step buttons.
"""

import wx

class ManualControlPanel(wx.Panel):
    """
    Panel with controls for manually moving the scanner through use of a
    slider and step buttons.
    """
    def __init__(self, parent, scanner):
        self.scanner = scanner
        wx.Panel.__init__(self, parent, wx.ID_ANY)
                
        self.vsizer = wx.BoxSizer(wx.VERTICAL)
        
        self.step_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        self.slider = wx.Slider(self, wx.ID_ANY, 0, -900, 900, style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS)
        self.vsizer.AddSpacer(10)
        self.vsizer.Add(self.slider, 0, wx.EXPAND | wx.ALIGN_CENTRE_HORIZONTAL | wx.LEFT | wx.RIGHT, border=5)
        self.vsizer.AddSpacer(10)
        self.vsizer.Add(self.step_button_sizer, 0, wx.ALIGN_CENTRE_HORIZONTAL)
        self.vsizer.AddSpacer(10)
        self.vsizer.AddStretchSpacer()
             
        self.l_big_step_button = wx.Button(self, wx.ID_ANY, '<<')
        self.l_small_step_button = wx.Button(self, wx.ID_ANY, '<')
        self.r_big_step_button = wx.Button(self, wx.ID_ANY, '>>')
        self.r_small_step_button = wx.Button(self, wx.ID_ANY, '>')
        
        self.step_button_sizer.AddSpacer(20)
        self.step_button_sizer.Add(self.l_big_step_button, 0, wx.ALIGN_CENTER)
        self.step_button_sizer.Add(self.l_small_step_button, 0, wx.ALIGN_CENTER)
        self.step_button_sizer.Add(self.r_small_step_button, 0, wx.ALIGN_CENTER)
        self.step_button_sizer.Add(self.r_big_step_button, 0, wx.ALIGN_CENTER)
        self.step_button_sizer.AddSpacer(20)
        
        #fix the tabbing order for the buttons
        self.r_small_step_button.MoveBeforeInTabOrder(self.r_big_step_button)         
        
        self.slider.SetFocus()
               
        self.SetSizer(self.vsizer)
        self.vsizer.Fit(self)
        self.SetAutoLayout(1)
               
        #register event handlers
        wx.EVT_BUTTON(self, self.l_small_step_button.GetId(), self.on_l_small_step)
        wx.EVT_BUTTON(self, self.l_big_step_button.GetId(), self.on_l_big_step)
        wx.EVT_BUTTON(self, self.r_small_step_button.GetId(), self.on_r_small_step)
        wx.EVT_BUTTON(self, self.r_big_step_button.GetId(), self.on_r_big_step)
        wx.EVT_COMMAND_SCROLL_CHANGED(self, self.slider.GetId(), self.on_slider_scroll)

        self.Show()
          
    
    def step(self, amount, control_to_focus_on):
        """
        Move the scanner by 'amount' degrees (may be negative). This
        method blocks until the scanner has finished moving to the new position
        (disabling all the controls in the panel while it moves) and then selects
        'control_to_focus_on' as the active control, where 'control_to_focus_on'
        is a wx.Control.
        """
        wx.BeginBusyCursor()
        self.disable_controls()
        
        #check if we have exceeded the slider limits
        current_slider_pos = self.slider.GetValue()
        new_slider_pos = current_slider_pos + amount
        
        if new_slider_pos > 900 or new_slider_pos < -900:
            wx.EndBusyCursor()
            return
        
        #update the slider position
        self.slider.SetValue(new_slider_pos)
        
        #change the scanner position
        if not self.scanner.is_engaged():
            self.scanner.engage()
        self.scanner.step(amount / 10.0)
        self.scanner.disengage()
        
        wx.EndBusyCursor()
        
        #calling enable_controls directly leads to the slider not being re-enabled on Windows
        wx.CallAfter(self.enable_controls,control_to_focus_on)

    
    
    def set_scan_pos(self, pos, control_to_focus_on):
        """
        Set the scanner position to 'pos' degrees (based on the zenith that has
        been set being 90).This method blocks until the scanner has finished moving 
        to the new position (disabling all the controls in the panel while it moves) 
        and then selects 'control_to_focus_on' as the active control, where 
        'control_to_focus_on' is a wx.Control.
        """
        
        self.disable_controls()
        wx.BeginBusyCursor()
                
        if not self.scanner.is_engaged():
            self.scanner.engage()
        self.scanner.set_position(pos)
        self.scanner.disengage()
        
        wx.EndBusyCursor()
        
        #calling enable_controls directly leads to the slider not being re-enabled on Windows
        wx.CallAfter(self.enable_controls,control_to_focus_on)


    def disable_controls(self):
        """
        Disables all the controls in the panel (the slider and all the 
        buttons).
        """
        self.slider.Disable()
        self.l_small_step_button.Disable()
        self.r_small_step_button.Disable()
        self.r_big_step_button.Disable()
        self.l_big_step_button.Disable()

   
    def enable_controls(self, control_to_focus_on):
        """
        Enables all the controls in the panel and gives focus
        to 'control_to_focus_on', where 'control_to_focus_on'
        is a wx.Control object.
        """
        self.slider.Enable()
        self.l_small_step_button.Enable()
        self.r_small_step_button.Enable()
        self.r_big_step_button.Enable()
        self.l_big_step_button.Enable()
        control_to_focus_on.SetFocus()  
        
        
    def on_l_small_step(self, event):
        """
        Event handler for handling button click events on the 
        '<' (small step left) button. Moves the scanner by -0.1 
        degrees from its current position.
        """
        self.step(-1, self.l_small_step_button)
    
    
    def on_l_big_step(self, event):
        """
        Event handler for handling button click events on the 
        '<<' (big step left) button. Moves the scanner by -1 
        degrees from its current position.
        """
        self.step(-10, self.l_big_step_button)
    
    
    def on_r_small_step(self, event):
        """
        Event handler for handling button click events on the 
        '<' (small step right) button. Moves the scanner by +0.1 
        degrees from its current position.
        """
        self.step(1, self.r_small_step_button)
    
    
    def on_r_big_step(self, event):
        """
        Event handler for handling button click events on the 
        '<<' (big step right) button. Moves the scanner by +1 
        degrees from its current position.
        """
        self.step(10, self.r_big_step_button)
    
    
    def on_slider_scroll(self, event):
        """
        Event handler for handling slider scroll events. Sets the 
        scanner to the position of the slider.
        """
        self.set_scan_pos((self.slider.GetValue() / 10.0) + 90.0, self.slider)



class InteractiveControlPanel(ManualControlPanel):
    """
    Panel with controls for manually moving the scanner through use of a
    slider and step buttons. An indicator above the slider displays the 
    current position of the scanner.
    """
    def __init__(self, parent, scanner):
        ManualControlPanel.__init__(self, parent, scanner)
        
        self.pos_text = wx.StaticText(self, wx.ID_ANY, u"Current Position: 90.0\u00b0")
        self.vsizer.Insert(0, self.pos_text, 0, wx.ALIGN_CENTER_HORIZONTAL)
        self.vsizer.InsertSpacer(0, 10)
        
        wx.EVT_COMMAND_SCROLL_THUMBTRACK(self, self.slider.GetId(), self.on_slider_track)
    
    
    def on_slider_track(self, evnt):
        """
        Event handler for slider track events. This method updates the position of the
        scanner displayed by the text above the slider to show the position that the 
        scanner will move to when the slider is released.
        """
        pos = self.slider.GetValue()
        self.pos_text.SetLabel(u"Current Position: " + str(round(90.0 + (pos / 10.0), 1)) + u"\u00b0")
    
    
    def step(self, amount, control_to_focus_on):
        """
        Move the scanner by 'amount' degrees (may be negative). This
        method blocks until the scanner has finished moving to the new position
        (disabling all the controls in the panel while it moves) and then selects
        'control_to_focus_on' as the active control, where 'control_to_focus_on'
        is a wx.Control.
        """
        ManualControlPanel.step(self, amount, control_to_focus_on)
        pos = self.scanner.get_position()
        self.pos_text.SetLabel(u"Current Position: " + str(round(pos, 1)) + u"\u00b0")
    
    
    def set_scan_pos(self, pos, control_to_focus_on):
        """
        Set the scanner position to 'pos' degrees (based on the zenith that has
        been set being 90).This method blocks until the scanner has finished moving 
        to the new position (disabling all the controls in the panel while it moves) 
        and then selects 'control_to_focus_on' as the active control, where 
        'control_to_focus_on' is a wx.Control.
        """
        ManualControlPanel.set_scan_pos(self, pos, control_to_focus_on)
        pos = self.scanner.get_position()
        self.pos_text.SetLabel(u"Current Position: " + str(round(pos, 1)) + u"\u00b0")
    
    
    def sync_with_scanner(self):
        """
        Reads the current position from the scanner and updates the slider appropriately.
        You only need to call this if the scanner has been moved by something other than
        the InteractiveControlPanel. For example, when switching back to the 
        InteractiveControlPanel following a scan.
        """
        pos = self.scanner.get_position()
        self.pos_text.SetLabel(u"Current Position: " + str(round(pos, 1)) + u"\u00b0")
        
        
